/**
 * $Header: /data/cvs/gsd/src/java/com/gs/common/dao/jdbc/DBConnectionPool.java,v 1.3 2007/06/14 02:18:11 clarkliu Exp $
 * $Revision: 1.3 $
 * $Date: 2007/06/14 02:18:11 $
 *
 * ====================================================================
 *
 * Copyright (c) 2001 Media Data Systems Pte Ltd All Rights Reserved.
 * This software is the confidential and proprietary information of
 * Media Data Systems Pte Ltd.You shall not disclose such Confidential
 * Information.
 *
 * ====================================================================
 *
 */
package io.renren.utils.util.db.connection;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Properties;
import java.util.Vector;

import org.apache.log4j.Logger;

/**
 * Holds and manages a set of database connections.
 *
 * @author $Author: clarkliu $
 * @version $Revision: 1.3 $ $Date: 2007/06/14 02:18:11 $
 */
public class DBConnectionPool {

	// ----------------------------------------------------- Static Variables
	static Logger logger = Logger.getLogger(DBConnectionPool.class);

	// ----------------------------------------------------- Instance Variables
	final private long REAPER_DELAY = 60000;

	final private long CONNECTION_TIMOUT_PERIOD = 120000;

	private Vector connections;

	private String poolName, url, user, password;

	private ConnectionReaper reaper;

	private int poolsize;

	private HashMap stackMap;

	// ----------------------------------------------------- Constructors
	public DBConnectionPool(String poolName, String url, String user, String password, int poolsize) {
		this.poolName = poolName;
		this.url = url;
		this.user = user;
		this.password = password;
		this.poolsize = poolsize;
		connections = new Vector(poolsize);
		reaper = new ConnectionReaper(this);
		stackMap = new HashMap();
		reaper.start();
	}

	// ----------------------------------------------------- Public methods
	public synchronized void reapConnections() {
		long stale = System.currentTimeMillis() - CONNECTION_TIMOUT_PERIOD;
		Enumeration connlist = connections.elements();

		while ((connlist != null) && (connlist.hasMoreElements())) {
			DBConnection conn = (DBConnection) connlist.nextElement();

			if ((conn.inUse()) && (stale > conn.getLastUse()) && (!conn.validate())) {
				/*
				 * The connection is dead if
				 * - The connection is flagged as being in use.
				 * - The connection is older than a preset connection time out.
				 * - The connection fails a validation check.
				 * Remove the connection from the pool. Let the other threads atlease proceed
				 */
				logger.warn("Killing dead connection");
				removeConnection(conn);
			} else if ((!conn.inUse()) && (!conn.validate())) {
				/*
				 * The connection is dead if
				 * - The connection is not flagged as being in use.
				 * - The connection fails a validation check.
				 * Remove the connection from the pool.
				 */
				logger.warn("Killing dead connection");
				removeConnection(conn);
			}
		}
	}

	public void logLeasedConnections() {
		Iterator it = connections.iterator();
		while (it.hasNext()) {
			DBConnection conn = (DBConnection) it.next();
			if (conn.inUse()) {
				logger.warn(conn);
				Throwable t = (Throwable) stackMap.get(conn);
				if (t == null)
					logger.warn("null stack trace stored");
				else
					logger.warn("", t);
			}
		}
	}

	public synchronized void closeConnections() {
		Enumeration connlist = connections.elements();

		while ((connlist != null) && (connlist.hasMoreElements())) {
			DBConnection conn = (DBConnection) connlist.nextElement();
			removeConnection(conn);
		}
	}

	public synchronized void removeConnection(DBConnection conn) {
		logger.warn("Removing Connection from " + poolName);
		//try to close connection before remove, add by clark
		try {
			conn.close();
		} catch (SQLException sqle) {
			logger.warn("Cannot return connection", sqle);
		}
		try {
			if (conn.getConnection() != null && !conn.getConnection().isClosed())
				conn.getConnection().close();
		} catch (SQLException sqle) {
			logger.warn("Cannot close connection", sqle);
		}
		//
		connections.removeElement(conn);
	}

	public synchronized Connection getConnection() throws SQLException {
		if (connections.size() > poolsize) {
			logger.warn("TOO MANY CONNECTIONS CREATED");
		}
		while (true) {
			//monitor start
//			logger.info("max conection pool size：" + poolsize);
			logger.info("current conection pool size：" + connections.size());
			DBConnection cc;
			for (int j = 0; j < connections.size(); j++) {
				cc = (DBConnection)connections.elementAt(j);
				if (cc.inUse()){
					logger.info(connections.elementAt(j) + ",inUse");
				}
			}
			//monitor end
			DBConnection c;
			for (int i = 0; i < connections.size(); i++) {
				c = (DBConnection) connections.elementAt(i);
				if (c.lease() && c.validate()) {
					if (logger.isDebugEnabled()) {
						try {
							throw new Exception();
						} catch (Throwable t) {
							stackMap.put(c, t);
						}
					}
					return c;
				}
			}
			if (connections.size() < poolsize) {
				addNewConnection();
			} else {
				if (logger.isDebugEnabled()) {
					logger.debug("Waiting for Connection from pool - " + this.poolName);
					logLeasedConnections();
				}
				try {
					logger.warn("Waiting for connection ... ");
					wait();
				} catch (InterruptedException ee) {
					logger.warn("Interrupted in getting connection", ee);
				}
			}
		}

	}

	protected synchronized void addNewConnection() throws SQLException {
		Properties prop = new Properties ();
		prop.put ("user", user);
		prop.put ("password", password);
		// 30 senconds
		prop.put ("oracle.jdbc.ReadTimeout", "30000");
		Connection conn = DriverManager.getConnection (url, prop);

		//Connection conn = DriverManager.getConnection(url, user, password);
		DBConnection c = new DBConnection(conn, this);
		connections.addElement(c);
		notifyAll();
		//if (logger.isDebugEnabled()) {       
		logger.warn("Creating new Connection (" + poolName + ") " + c);
		//}
	}

	public synchronized void returnConnection(DBConnection conn) {
		conn.expireLease();
		notifyAll();
	}
	
	public String getPoolName () {
		return poolName;
	}

	class ConnectionReaper extends Thread {

		private DBConnectionPool pool;

		ConnectionReaper(DBConnectionPool pool) {
			this.pool = pool;
			setDaemon(true);
		}

		public void run() {
			while (true) {
				try {
					sleep(REAPER_DELAY);
				} catch (InterruptedException e) {
					logger.warn("Interrupted in connection reaper", e);
				}
				pool.reapConnections();
			}
		}
	}

}
